--[[
	SpellChecker2 v2.9
 
	By sarf

	This mod allows you to auto-corrects messages, both the ones that you 
	send as well as the ones you receive. It also allows for experimental 
	on-the-fly correction.
	
	It is, in its core, an updated version of SpellChecker.

	Thanks goes to Bhaerau for suggesting it.
	Special thanks to Legorol and AnduinLothar for nagging me about on-the-fly correction,
	as well as about a binding for the people who may be less enthusiastic about spell 
	correctors than me.
	
	Check out SpellChecker2_Compatibility.lua for a list of addons that it is natively compatible with.
	
   ]]


SpellChecker2_Options = {
};

SpellChecker2_ChannelsToAvoid = {
};

SpellChecker2_User_Corrections = {
};

SpellChecker2_User_Corrections_Raw = {
};

-- session-specific variables
SpellChecker2_Setup = {
};

function SpellChecker2_OnLoad()
	this:RegisterEvent("VARIABLES_LOADED");
	this:RegisterEvent("ADDON_LOADED");

	if ( type(hooksecurefunc) == "function" ) then
		hooksecurefunc("ChatEdit_ParseText", SpellChecker2_FixEditBox)
	else
		ChatFrame1:AddMessage("SpellChecker2: Going to backup hook method.");
		SpellChecker2_Saved_ChatEdit_ParseText = ChatEdit_ParseText;
		ChatEdit_ParseText = SpellChecker2_ChatEdit_ParseText;
	end
end	

function SpellChecker2_OnEvent()
	if ( event == "VARIABLES_LOADED" ) then
		SpellChecker2_VariablesLoaded();
	elseif( event == "ADDON_LOADED" ) then
		SpellChecker2_CompatibilityCheck();
	end
end	

function SpellChecker2_VariablesLoaded()
	if ( not SpellChecker2_Options ) then
		SpellChecker2_Options = {};
	end
	for k, v in pairs(SPELLCHECKER2_OPTIONS_DEFAULT) do
		if ( SpellChecker2_Options[k] == nil ) then
			SpellChecker2_Options[k] = v;
		end
	end
	local cmdName = "SPELLCHECKER2";
	SlashCmdList[cmdName] = SpellChecker2_SlashCommand;
	for k, v in pairs(SPELLCHECKER2_SLASH_COMMANDS) do
		setglobal("SLASH_"..cmdName..k, v);
	end
	SpellChecker2_CompatibilityCheck();

-- Hooking functions

	SpellChecker2_Saved_SendChatMessage = SendChatMessage;
	SendChatMessage = SpellChecker2_SendChatMessage;
	
	SpellChecker2_Saved_ChatFrame_OnEvent = ChatFrame_OnEvent;
	ChatFrame_OnEvent = SpellChecker2_ChatFrame_OnEvent;

-- Options

	SpellChecker2_Options_VariablesLoaded();
	
end

function SpellChecker2_GetNextArgument(msg)
	local index;
	index = string.find(msg, " ");
	if ( index ) then
		return strsub(msg, 1, index-1), strsub(msg, index+1);
	else
		return msg, "";
	end
end

function SpellChecker2_SlashCommand(msg)
	local command, params = SpellChecker2_GetNextArgument(msg);
	for k, v in pairs(SPELLCHECKER2_SLASH_GUI) do
		if ( command == v ) then
			SpellChecker2_SlashCommand_GUI();
			return;
		end
	end
	for k, v in pairs(SPELLCHECKER2_SLASH_ADD) do
		if ( command == v ) then
			SpellChecker2_SlashCommand_AddCorrection(SpellChecker2_GetNextArgument(params));
			return;
		end
	end
	for k, v in pairs(SPELLCHECKER2_SLASH_ADD_RAW) do
		if ( command == v ) then
			SpellChecker2_SlashCommand_AddRawCorrection(SpellChecker2_GetNextArgument(params));
			return;
		end
	end
	for k, v in pairs(SPELLCHECKER2_SLASH_REMOVE) do
		if ( command == v ) then
			SpellChecker2_SlashCommand_RemoveCorrection(SpellChecker2_GetNextArgument(params));
			return;
		end
	end
	for k, v in pairs(SPELLCHECKER2_SLASH_ON_THE_FLY) do
		if ( command == v ) then
			SpellChecker2_SlashCommand_OnTheFly(SpellChecker2_GetNextArgument(params));
			return;
		end
	end
	SpellChecker2_SlashCommand_ShowUsage();
end

function SpellChecker2_SlashCommand_ShowUsage()
	for k, v in pairs(SPELLCHECKER2_SLASH_USAGE) do
		SpellChecker2_ShowLine(v);
	end
end

function SpellChecker2_SlashCommand_AddCorrection(thingToCorrect, correctThing)
	if ( thingToCorrect ) and ( strlen(thingToCorrect) > 0 ) 
		and ( correctThing ) and ( strlen(correctThing) > 0 ) then
		SpellChecker2_AddCorrection(thingToCorrect, correctThing);
		SpellChecker2_ShowLine(format(SPELLCHECKER2_MSG_ADD_CORRECTION, thingToCorrect, correctThing));
	else
		SpellChecker2_ShowLine(SPELLCHECKER2_MSG_EMPTY_CORRECTION);
	end
end

function SpellChecker2_SlashCommand_AddRawCorrection(thingToCorrect, correctThing)
	if ( thingToCorrect ) and ( strlen(thingToCorrect) > 0 ) 
		and ( correctThing ) and ( strlen(correctThing) > 0 ) then
		SpellChecker2_AddRawCorrection(thingToCorrect, correctThing);
		SpellChecker2_ShowLine(format(SPELLCHECKER2_MSG_ADD_RAW_CORRECTION, thingToCorrect, correctThing));
	else
		SpellChecker2_ShowLine(SPELLCHECKER2_MSG_EMPTY_CORRECTION);
	end
end

function SpellChecker2_SlashCommand_RemoveCorrection(thingToCorrect)
	if ( thingToCorrect ) and ( strlen(thingToCorrect) > 0 ) then
		SpellChecker2_RemoveCorrection(thingToCorrect);
		SpellChecker2_ShowLine(format(SPELLCHECKER2_MSG_REMOVE_CORRECTION, thingToCorrect));
	else
		SpellChecker2_ShowLine(SPELLCHECKER2_MSG_EMPTY_CORRECTION);
	end
end

function SpellChecker2_SlashCommand_OnTheFly(command)
	local oldState = SpellChecker2_Options.onTheFlyCorrection;
	local newState = true;
	if oldState then newState = false; end
	local value = SPELLCHECKER2_STATE_BOOLEAN_TRUE;
	if ( not newState ) then
		value = SPELLCHECKER2_STATE_BOOLEAN_FALSE;
	end
	SpellChecker2_ShowLine(format(SPELLCHECKER2_MSG_STATE_ON_THE_FLY, value));
	SpellChecker2_SetOnTheFlyCorrection(newState);
end

function SpellChecker2_SlashCommand_GUI()
	if ( not IsAddOnLoaded("SpellChecker2GUI") ) then
		LoadAddOn("SpellChecker2GUI");
	end
	if ( SpellChecker2_GUI_ToggleShow ) then
		SpellChecker2_GUI_ToggleShow();
	else
		SpellChecker2_ShowLine(SPELLCHECKER2_MSG_NYI);
	end
end

function SpellChecker2_ShowLine(line)
	if ( ChatFrame1 ) and ( ChatFrame1.AddMessage ) then
		ChatFrame1:AddMessage(line, 1, 1, 0.1);
	end
end

function SpellChecker2_AddCorrection(thingToCorrect, correctThing)
	thingToCorrect = SpellChecker2_EscapeString(thingToCorrect);
	correctThing = SpellChecker2_EscapeString(correctThing);
	local wl = SpellChecker2_WordList[thingToCorrect];
	if ( wl ) and ( wl == correctThing ) then
		SpellChecker2_User_Corrections[thingToCorrect] = nil;
		SpellChecker2_User_Corrections_Raw[thingToCorrect] = nil;
	else
		if ( thingToCorrect ) and ( strlen(thingToCorrect) > 0 ) 
			and ( correctThing ) and ( strlen(correctThing) > 0 ) then
			SpellChecker2_User_Corrections[thingToCorrect] = correctThing;
			SpellChecker2_User_Corrections_Raw[thingToCorrect] = nil;
		end
	end
end

function SpellChecker2_EscapeString(str)
	if ( type(str) ~= "string" ) or ( strlen(str) <= 0 ) then
		return nil;
	end
	str = string.gsub(str, "%%", "%%%%");
	for k,v in pairs(SPELLCHECKER2_ESCAPES) do
		str = string.gsub(str, k, v);
	end
	return str;
end

function SpellChecker2_AddRawCorrection(thingToCorrect, correctThing)
	thingToCorrect = SpellChecker2_EscapeString(thingToCorrect);
	correctThing = SpellChecker2_EscapeString(correctThing);
	local wl = SpellChecker2_WordList[thingToCorrect];
	if ( wl ) and ( wl == correctThing ) then
		SpellChecker2_User_Corrections[thingToCorrect] = nil;
		SpellChecker2_User_Corrections_Raw[thingToCorrect] = nil;
	else
		if ( thingToCorrect ) and ( strlen(thingToCorrect) > 0 ) 
			and ( correctThing ) and ( strlen(correctThing) > 0 ) then
			SpellChecker2_User_Corrections[thingToCorrect] = nil;
			SpellChecker2_User_Corrections_Raw[thingToCorrect] = correctThing;
		end
	end
end

function SpellChecker2_RemoveCorrection(thingToCorrect)
	local originalThingToCorrect = thingToCorrect;
	thingToCorrect = SpellChecker2_EscapeString(thingToCorrect);
	if ( thingToCorrect ) and ( strlen(thingToCorrect) > 0 ) then
		if ( SpellChecker2_WordList[thingToCorrect] ) then
			SpellChecker2_User_Corrections[thingToCorrect] = thingToCorrect;
		else
			SpellChecker2_User_Corrections[thingToCorrect] = nil;
		end
		SpellChecker2_User_Corrections[originalThingToCorrect] = nil;
		SpellChecker2_User_Corrections_Raw[thingToCorrect] = nil;
		SpellChecker2_User_Corrections_Raw[originalThingToCorrect] = nil;
	end
end

function SpellChecker2_CorrectionFuncGsub(str, sep)
	if ( not str ) then return; end
	if ( str ~= "" ) and ( not strfind(str, "|Hitem:") ) and ( not strfind(str, "|Hplayer:") )  then
		local user = SpellChecker2_User_Corrections[str];
		local wl = nil;
		if ( not user ) then
			wl = SpellChecker2_WordList[str];
		end
		if ( user ) then
			str = user;
		elseif ( wl ) then
			str = wl;
		end
	end
	if ( sep ) then
		return str..sep;
	else 
		return str;
	end
end

function SpellChecker2_AddCorrectionChecker(func)
	if ( type(func) ~= "function" ) then
		return false;
	end
	if ( not SpellChecker2_Setup.correctionFuncs ) then
		SpellChecker2_Setup.correctionFuncs = {};
	end
	SpellChecker2_Setup.correctionFuncs[func] = true;
	return true;
end

function SpellChecker2_RemoveCorrectionChecker(func)
	if ( type(func) ~= "function" ) then
		return false;
	end
	if ( not SpellChecker2_Setup.correctionFuncs ) then
		return false;
	end
	SpellChecker2_Setup.correctionFuncs[func] = nil;
	return true;
end


function SpellChecker2_ShouldNotCorrectMessage(msg)
	if ( type(msg) ~= "string" ) then return false; end
	-- my attempt to be compatible with most addons sending info.
	if ( strsub(msg, 1, 1) == "<" ) then
		return true;
	end
	if ( SpellChecker2_Setup.correctionFuncs ) then
		for k, v in pairs(SpellChecker2_Setup.correctionFuncs) do
			if ( v == true ) then
				if ( k(msg) == true ) then
					return true;
				end
			end
		end
	end
	return false;
end


function SpellChecker2_CorrectMessage(msg, noStartCapitalization)
	local firstChar = strsub(msg, 1, 1);
	if ( SpellChecker2_ShouldNotCorrectMessage(msg) ) then
		return msg;
	end
	local SpellChecker2_CorrectionStr = "";
	SpellChecker2_CorrectionStr = string.gsub(msg, SPELLCHECKER2_CORRECTIONEXPRESSION, SpellChecker2_CorrectionFuncGsub);
	if ( SpellChecker2_User_Corrections_Raw ) then
		for k,v in pairs(SpellChecker2_User_Corrections_Raw) do
			SpellChecker2_CorrectionStr = string.gsub(SpellChecker2_CorrectionStr, k, v);
		end
	end
	local i = strfind(SpellChecker2_CorrectionStr, "tp://");
	if ( i ) and ( i <= 3 ) then
		noStartCapitalization = true;
	end
	if ( not noStartCapitalization ) and ( SpellChecker2_Options.startCapitalization ) then
		firstChar = strsub(SpellChecker2_CorrectionStr, 1, 1);
		local extended = (strbyte(firstChar,1)==195);
		if ( extended ) then firstChar = strsub(SpellChecker2_CorrectionStr, 1, 2); end
		firstChar = strupper(firstChar);
		local i = 2;
		if (extended) then i = 3; end
		SpellChecker2_CorrectionStr = firstChar..strsub(SpellChecker2_CorrectionStr, i);
	end
	return SpellChecker2_CorrectionStr;
end

function SpellChecker2_MatchChannelsInList(channel, number, channelsList)
	if ( not channel ) then
		return false;
	end
	if ( not channelsList ) then
		return false;
	end
	if ( channel == "CHANNEL" ) then
		if ( not number ) then
			return false;
		end
		channel = channel..number;
	end
	for k, v in pairs(channelsList) do
		if ( v == channel ) then
			return true;
		end
	end
	return false;
end


function SpellChecker2_SendChatMessage(msg, msgType, language, number, p1, p2, p3, p4, p5)
	if ( SpellChecker2_Setup.ignoreNextMessage ) 
		or ( not SpellChecker2_Options.enabled ) 
		or ( not SpellChecker2_Options.correctOutgoingMessages ) then
		SpellChecker2_Setup.ignoreNextMessage = false;
	elseif ( msgType ) then
		local correct = false;
		correct = SpellChecker2_MatchChannelsInList(msgType, number, SpellChecker2_Options.channelsToCorrect);

		if ( correct ) then
			if ( SpellChecker2_MatchChannelsInList(msgType, number, SpellChecker2_ChannelsToAvoid) ) then
				correct = false;
			end
		end
		if ( correct ) then
			msg = SpellChecker2_CorrectMessage(msg, (msgType=="EMOTE"));
		end
	end
	return SpellChecker2_Saved_SendChatMessage(msg, msgType, language, number, p1, p2, p3, p4, p5);
end

function SpellChecker2_GetEventList()
	if ( SpellChecker2_Setup.eventListDirty ) or ( not SpellChecker2_Setup.eventList ) then
		if ( not SpellChecker2_Options ) or ( not SpellChecker2_Options.channelsToCorrect ) then
			return nil;
		end
		if ( not SpellChecker2_Setup.eventList ) then
			SpellChecker2_Setup.eventList = {};
		else
			for k, v in pairs(SpellChecker2_Setup.eventList) do
				SpellChecker2_Setup.eventList[k] = false;
			end
		end
		local ctg = nil;
		for k, v in pairs(SpellChecker2_Options.channelsToCorrect) do
			ctg = ChatTypeGroup[v];
			if ( ctg ) then
				for key, event in pairs(ctg) do
					SpellChecker2_Setup.eventList[event] = true;
				end
			end
		end
	end
	return SpellChecker2_Setup.eventList;
end

function SpellChecker2_ChatFrame_OnEvent(self, event, ...)
	local correct = true;
	if ( not SpellChecker2_Options.ignoranceIsBliss ) then
		if ( arg2 == UnitName("player") ) then
			correct = false;
		end
	end
	if ( not SpellChecker2_Options.enabled ) or ( not SpellChecker2_Options.correctIncomingMessages ) then
		correct = false;
	end
	if ( correct ) then
		correct = false;
		local eventList = SpellChecker2_GetEventList();
		if ( eventList[event] ) then
			correct = true;
		elseif ( event == "CHAT_MSG_CHANNEL" ) then
			correct = SpellChecker2_MatchChannelsInList("CHANNEL", tonumber(arg8), SpellChecker2_Options.channelsToCorrect);
	
			if ( correct ) then
				if ( SpellChecker2_MatchChannelsInList("CHANNEL", tonumber(arg8), SpellChecker2_ChannelsToAvoid) ) then
					correct = false;
				end
			end
		end
		if ( correct ) then
			arg1 = SpellChecker2_CorrectMessage(arg1, true);
		end
	end
	return SpellChecker2_Saved_ChatFrame_OnEvent(self, event, ...);
end

function SpellChecker2_IsSecureCommand(text)
	local command = strmatch(text, "^(/[^%s]+)") or "";
	
	return IsSecureCmd(command);
end

function SpellChecker2_IsSecureCommand_Old(text)
	local command = strmatch(text, "^(/[^%s]+)") or "";

	command = strupper(command);
	-- Ugly hack that copies all (known) secure commands.
	if ( SPELLCHECKER2_SECURE_COMMANDS ) then
		for index, commandName in pairs(SPELLCHECKER2_SECURE_COMMANDS) do
			local base = "SLASH_"..commandName;
			local i = 1;
			local cmdString = TEXT(getglobal(base..i));
			while ( cmdString ) do
				cmdString = strupper(cmdString);
				if ( cmdString == command ) then
					return true;
				end
				i = i + 1;
				cmdString = TEXT(getglobal(base..i));
			end
		end
	end
	return false;
end

SpellChecker2_ChatEdit_ParseText_IsParsing = false;

function SpellChecker2_FixEditBox(editBox, send, p1, p2, p3, p4, p5)
	local shouldCorrect = false;
	if ( type(SpellChecker2_Options) == "table" ) then
		shouldCorrect = SpellChecker2_Options.onTheFlyCorrection;
	end
	if ( shouldCorrect ) and ( not SpellChecker2_ChatEdit_ParseText_IsParsing ) then
		SpellChecker2_ChatEdit_ParseText_IsParsing = true;
		local text = editBox:GetText();
	
		if ( not text ) or ( strlen(text) <= 0 ) then
			shouldCorrect = false;
		end
		if ( shouldCorrect ) and ( SpellChecker2_IsSecureCommand(text) ) then
			shouldCorrect = false;
		end
		if ( shouldCorrect ) then
			local slashCmd = "SLASH_SCRIPT";
			local i = 1;
			local cmd = getglobal(slashCmd..i);
			while ( cmd ) do
				if ( strsub(text, 1, strlen(cmd)) == cmd ) then
					shouldCorrect = false;
					break;
				end
				i = i + 1;
				cmd = getglobal(slashCmd..i)
			end
		end
		if ( shouldCorrect ) then
			slashCmd = "SLASH_SPELLCHECKER2";
			i = 1;
			cmd = getglobal(slashCmd..i);
			while ( cmd ) do
				if ( strsub(text, 1, strlen(cmd)) == cmd ) then
					shouldCorrect = false;
					break;
				end
				i = i + 1;
				cmd = getglobal(slashCmd..i)
			end
		end
		
		if ( shouldCorrect ) then
			text = SpellChecker2_CorrectMessage(text, true);
			--local oldHandler = editBox:GetScript("OnUpdate");
			--editBox:SetScript("OnUpdate", nil);
			editBox:SetText(text);
			--editBox:SetScript("OnUpdate", oldHandler);
		end
		SpellChecker2_ChatEdit_ParseText_IsParsing = false;
	end
end


function SpellChecker2_ChatEdit_ParseText(editBox, send, p1, p2, p3, p4, p5)
	SpellChecker2_FixEditBox(editBox, send, p1, p2, p3, p4, p5);
	return SpellChecker2_Saved_ChatEdit_ParseText(editBox, send, p1, p2, p3, p4, p5);
end